package com.k8.common.aop.javassist;

import com.k8.common.aop.AdvisedSupport;
import com.k8.common.aop.AopProxy;
import com.k8.common.aop.JavassistProxyFactory;
import com.k8.common.aop.internal.interceptor.ExposeInvocationInterceptor;
import com.k8.common.aop.internal.interceptor.MethodInterceptor;
import com.k8.common.aop.internal.invocation.MethodInvocation;
import com.k8.common.aop.internal.invocation.ReflectiveMethodInvocation;
import com.k8.common.util.AopUtil;
import com.k8.common.util.ClassUtil;
import javassist.*;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

//todo 测试

/**
 * 动态javassist动态编译对应的class，将需要进行拦截的方法进行拦截
 * 此外对MethodInvocation进行派生，原本执行method.invoke的代码单独抽象出一个方法
 * 在派生类中不在调用method.invoke转而直接调用一个动态编译生成的函数式接口(每个方法对应动态编译成DynamicFUnction)，直接调用对象的方法
 * 且不同于之前的methodKey，在拦截器识别该方法需要对应哪个拦截器链时，直接给定索引index即可，因为是提前编译好的
 */
//注意$的使用，例如获取内部类，不应该是ClassName.IClassName,而是ClassName$IClassName
public class JavassistAopProxy implements AopProxy {
    private final AdvisedSupport advisedSupport;
    private final JavassistProxyFactory.NamingPolicy namingPolicy;

    private final ClassPool localPool;
    private final static String INTERCEPTOR = "k8$interceptor";
    private final static String classIndexInvokeName = "invoke";
    private final CtClass ctClassIndex;
    private final CtClass[] ctClassIndexInvokeParameters;
    private final CtClass ctClassIndexInvokeReturnType;

    public JavassistAopProxy(AdvisedSupport advisedSupport, JavassistProxyFactory.NamingPolicy namingPolicy) {
        this.namingPolicy = namingPolicy;
        this.advisedSupport = advisedSupport;
        this.localPool = ClassPool.getDefault();
        this.localPool.appendClassPath(new ClassClassPath(this.getClass().getClassLoader().getClass()));


        try {
            this.ctClassIndex = localPool.get(ClassIndex.class.getName());
            this.ctClassIndexInvokeParameters = new CtClass[2];
            ctClassIndexInvokeParameters[0] = localPool.get(int.class.getName());
            ctClassIndexInvokeParameters[1] = localPool.get(Object[].class.getName());
            this.ctClassIndexInvokeReturnType = localPool.get(Object.class.getName());
        } catch (NotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object getProxy() {
        ClassLoader classLoader = ClassUtil.getClassLoader(advisedSupport.getTarget().getClass());
        return getProxy(classLoader);
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        Object target = advisedSupport.getTarget();
        ClassPool pool = ClassPool.getDefault();
        pool.appendClassPath(new LoaderClassPath(classLoader));
        Class<?> clazz = target.getClass();
        try {
            JavassistMethodInterceptor javassistMethodInterceptor = new JavassistMethodInterceptor(advisedSupport);
            CtClass proxyClass = pool.makeClass(namingPolicy.getProxyName(clazz));
            CtClass targetClazz = pool.get(clazz.getName());
            proxyClass.setSuperclass(targetClazz);
            //添加拦截器字段
            CtField interceptor = new CtField(pool.get(JavassistMethodInterceptor.class.getName()), INTERCEPTOR, proxyClass);
            proxyClass.addField(interceptor);
            //重写需要拦截的方法
            Method[] declaredMethods = clazz.getDeclaredMethods();
            List<Method> needProxyMethods = new LinkedList<>();
            int index = 0;
            for (Method method : declaredMethods) {
                List<MethodInterceptor> interceptors = advisedSupport.getChain(method);
                //todo 关闭动态连接器不为空检验进行检测
                if (!AopUtil.isNoOpMethod(method) && (interceptors != null && !interceptors.isEmpty())) {
                    //说明该方法需要动态代理
                    needProxyMethods.add(method);
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    CtClass[] ctParameters = new CtClass[parameterTypes.length];
                    for (int i = 0; i < parameterTypes.length; i++) {
                        Class<?> clazz0 = parameterTypes[i];
                        ctParameters[i] = AopUtil.getCtClassForPrimitive(clazz0, pool);
                    }
                    Class<?> returnType = method.getReturnType();
                    CtClass ctReturnType = pool.get(returnType.getName());
                    String body;
                    CtMethod ctMethod = new CtMethod(ctReturnType, method.getName(), ctParameters, proxyClass);
                    //invoke0(Method method , Object... args)
                    body = "int index = " + index + ";\n";
                    if (returnType == Void.TYPE) {
                        body = body + "this." + INTERCEPTOR + ".invoke0(index,$args);\n";
                    } else {
                        body = body + String.format(
                                "Object result = this.%s.invoke0(index,$args);\n" +
                                        "if (result == null) {\n" +
                                        "    return null;\n" +
                                        "}\n" +
                                        "return (%s) result;\n",
                                INTERCEPTOR,
                                returnType.getName()
                        );
                    }
                    body = "{" + body + "}";
                    ctMethod.setBody(body);
                    proxyClass.addMethod(ctMethod);
                    //createDynamicFunction为最后的方法调用
                    javassistMethodInterceptor.initMethodChain(index, method);
                    index++;
                }
            }
            //创建索引类
            ClassIndex classIndex = buildClassIndex(targetClazz, target, needProxyMethods);
            javassistMethodInterceptor.setClassIndex(classIndex);
            Object proxy = proxyClass.toClass().newInstance();
            Field declaredField = proxy.getClass().getDeclaredField(INTERCEPTOR);
            declaredField.setAccessible(true);
            declaredField.set(proxy, javassistMethodInterceptor);
            return proxy;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private static final String dynamic_target = "k8$target";

    private ClassIndex buildClassIndex(CtClass targetClass, Object target, List<Method> needProxyMethods) {
        CtClass proxy = localPool.makeClass(namingPolicy.getProxyName(ClassIndex.class));
        proxy.addInterface(ctClassIndex);
        try {
            CtField field = new CtField(targetClass, dynamic_target, proxy);
            proxy.addField(field);
            CtMethod ctMethod = new CtMethod(ctClassIndexInvokeReturnType, classIndexInvokeName, ctClassIndexInvokeParameters, proxy);
            ctMethod.setModifiers(ctMethod.getModifiers() | Modifier.VARARGS);
            int index = 0;
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("{");
            stringBuilder.append("int index = $1;\n");
            stringBuilder.append("Object[] args0 = (Object[]) $2;\n");
            stringBuilder.append("switch (index){\n");
            for (Method method : needProxyMethods) {
                String branch = buildMethodBranch(index, method);
                stringBuilder.append(branch);
                index++;
            }
            stringBuilder.append("default : \n" +
                    "throw new RuntimeException(\"This method branch not found.\");\n" +
                    "}");
            stringBuilder.append("}");
            ctMethod.setBody(stringBuilder.toString());
            proxy.addMethod(ctMethod);
            Class<?> proxyClass = proxy.toClass();
            ClassIndex classIndex = (ClassIndex) proxyClass.newInstance();
            Field field1 = classIndex.getClass().getDeclaredField(dynamic_target);
            field1.setAccessible(true);
            field1.set(classIndex, target);
            return classIndex;
        } catch (CannotCompileException | InstantiationException |
                 IllegalAccessException | NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    private String buildMethodBranch(int index, Method method) {//分支
        Class<?>[] parameterTypes = method.getParameterTypes();
        int paraIndex = 0;
        StringBuilder stringBuilder = new StringBuilder();
        for (Class<?> parType : parameterTypes) {
            stringBuilder.append(String.format("(%s)args0[%d]", parType.getName(), paraIndex));
            if (paraIndex + 1 < parameterTypes.length) {
                stringBuilder.append(",");
            }
            paraIndex++;
        }
        return String.format("case %d: \n" +
                        "Object result = this.%s.%s(%s);\n" +
                        "if(result == null){\n" +
                        "return null;\n" +
                        "}\n" +
                        "return (%s)result;\n"
                , index, dynamic_target, method.getName(), stringBuilder, method.getReturnType().getName());
    }

    @Override
    public Class<?> getProxyClass(ClassLoader classLoader) {
        return null;
    }

    public static class JavassistMethodInterceptor {
        //todo advisedSupport需要设置最后生成的代理类
        private final AdvisedSupport advisedSupport;

        private final List<Object[]> cacheMethodNode = new ArrayList<>();
        private ClassIndex classIndex;

        public JavassistMethodInterceptor(AdvisedSupport advisedSupport) {
            this.advisedSupport = advisedSupport;
        }

        public void setClassIndex(ClassIndex classIndex) {
            this.classIndex = classIndex;
        }

        //提前生成好拦截器链缓存
        public void initMethodChain(int index, Method method) throws NoSuchMethodException {
            Object[] methodNode = new Object[2];
            String methodKey = ClassUtil.getMethodToString(method);
            List<MethodInterceptor> methodInterceptors = getChain(methodKey);
            methodNode[0] = method;
            methodNode[1] = methodInterceptors;
            cacheMethodNode.add(index, methodNode);
        }

        private List<MethodInterceptor> getChain(String methodKey) throws NoSuchMethodException {
            List<MethodInterceptor> methodInterceptors = advisedSupport.getCacheAdvices().get(methodKey);
            if (methodInterceptors == null) throw new IllegalStateException("This method must have interceptors chain");
            return methodInterceptors;
        }

        //执行方法时被映射到本方法，创建动态mi
        public Object invoke0(int index, Object... args) throws Throwable {
            Object[] methodNode = cacheMethodNode.get(index);
            Method method = (Method) methodNode[0];
            List<MethodInterceptor> chain = (List<MethodInterceptor>) methodNode[1];
            List<MethodInterceptor> methodInterceptors = new ArrayList<>(chain);
            MethodInvocation methodInvocation = new DynamicReflectiveMethodInvocation(index, this.advisedSupport.getProxy(), this.advisedSupport.getTarget()
                    , method, args, advisedSupport.getTargetClass(), methodInterceptors, null, classIndex);
            ExposeInvocationInterceptor interceptor = new ExposeInvocationInterceptor();
            methodInterceptors.add(0, interceptor);
            return methodInvocation.proceed();
        }
    }

    public static class DynamicReflectiveMethodInvocation extends ReflectiveMethodInvocation {
        private ClassIndex classIndex;
        private int index;

        public DynamicReflectiveMethodInvocation(int index, Object proxy, Object target, Method method, Object[] arguments, Class<?> targetClass, List<MethodInterceptor> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy, ClassIndex classIndex) {
            super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers, methodProxy);
            this.classIndex = classIndex;
            this.index = index;
        }

        @Override
        protected Object invoke0() throws Throwable {
            return classIndex.invoke(index, getArguments());
        }
    }

}
